#include "thread.h"
#include "list.h"
#include <stdlib.h>
#include <stdio.h>
#include <ucontext.h>
#include <valgrind/valgrind.h>

// La taille par défaut de la pile d'un nouveau thread est de 2Mo
#define DEFAULT_STACK_SIZE 2048
#define DEAD 0
#define ACTIVE 1
#define TO_BE_DESTROYED 2
#define NON_ACTIVE 3

#define DEBUG

// thread principal
thread_info * main_thread = NULL;

// thread en cours d'exécution
thread_info * running_thread = NULL;

// liste des threads actifs
thread_list active_threads = NULL;


/* thread_attr_init(ucontext_t * attr)
 * Fonction initialisant le nouveau contexte à créer à l'aide d'un modèle de
 * contexte déjà existant.
 * Si on lui donne NULL en paramètre, la fonction réalise une initialisation
 * par défaut.
 * Retourne 0 en cas d'erreur, 1 sinon.
 */
int thread_attr_init(thread_info* thread, ucontext_t * attr) {
  if (attr == NULL) {
    thread->attr.uc_stack.ss_size = DEFAULT_STACK_SIZE;
    thread->attr.uc_stack.ss_sp = malloc(DEFAULT_STACK_SIZE);
    thread->valgrind_stackid = VALGRIND_STACK_REGISTER(thread->attr.uc_stack.ss_sp,thread->attr.uc_stack.ss_sp + thread->attr.uc_stack.ss_size);
    if (thread->attr.uc_stack.ss_sp == NULL) {
      perror("malloc");
      return 0;
    }
    thread->attr.uc_link = &(main_thread->attr);
  
  }
  else {
    thread->attr.uc_stack.ss_size = attr->uc_stack.ss_size;
    thread->attr.uc_stack.ss_sp = malloc(thread->attr.uc_stack.ss_size);
    thread->valgrind_stackid = VALGRIND_STACK_REGISTER(thread->attr.uc_stack.ss_sp,thread->attr.uc_stack.ss_sp + thread->attr.uc_stack.ss_size);
    if (NULL == (thread->attr.uc_stack.ss_sp)) {
      perror("malloc");
      return 1;
    }
    thread->attr.uc_link = attr->uc_link;
  }
  
  return 1;
}


void thread_init (){
  // On alloue la mémoire nécessaire pour nos thread_info.
    main_thread = malloc(sizeof(thread_info));
#ifdef DEBUG
    fprintf(stdout,"==== CREATION DU THREAD MAIN %p ====\n",(thread_t)main_thread);
#endif
    main_thread->attr.uc_stack.ss_size = DEFAULT_STACK_SIZE;
    main_thread->attr.uc_stack.ss_sp = malloc(DEFAULT_STACK_SIZE);
    main_thread->valgrind_stackid = VALGRIND_STACK_REGISTER(main_thread->attr.uc_stack.ss_sp,main_thread->attr.uc_stack.ss_sp + main_thread->attr.uc_stack.ss_size);
    // Si c'est le premier thread qu'on crée, main_thread est NULL, et l'on doit donc le lier au contexte actuel.
    getcontext(&(main_thread->attr));
    // On le déclare non joint
    main_thread->is_joined = 1;
    // On lui donne aussi l'identifiant 0.
    main_thread->id_thread = (thread_t)main_thread;
    // On le déclare actif
    main_thread->state = ACTIVE;
    // On place le résultat NULL
    main_thread->result = NULL;
    // On indique que le thread principal est celui qui s'exécute.
    running_thread = main_thread;
    // On ajoute le thread principal à la liste des threads en exécution.
    active_threads = thread_list_add_front(active_threads, (thread_t)main_thread);
}


/* recuperer l'identifiant du thread courant.
 */
thread_t thread_self(void) {
  if (main_thread == NULL)
    thread_init();
  return (thread_t)running_thread;
}


/* creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succès, -1 en cas d'erreur.
 */
int thread_create(thread_t* newthread, ucontext_t * attr, void * (*func)(void *), void * funcarg) {
  if (main_thread == NULL) {
    thread_init();
  } 

  thread_info* thread_to_create = malloc(sizeof(thread_info));  
#ifdef DEBUG
  fprintf(stdout,"==== CREATION DU THREAD %p ====\n",(thread_t)thread_to_create);
#endif
  thread_to_create->state = ACTIVE;
  thread_to_create->is_joined = 0;
  thread_to_create->id_thread = (thread_t)thread_to_create; 
  thread_to_create->function = func;
  thread_to_create->arg = funcarg;
  //  thread_to_create->id_thread = *newthread;
  *newthread = thread_to_create->id_thread;
  getcontext(&thread_to_create->attr);
    
  // On initialise le contexte à créer.
  if (thread_attr_init(thread_to_create, attr) == 0) {
    perror("thread_attr_init");
    return -1;      
  }

  // On crée le nouveau contexte du thread à créer
  makecontext(&(thread_to_create->attr),(void (*)(void))thread_wraper,0);
 
  thread_info* tmp = running_thread;
  active_threads = thread_list_add_front(active_threads, (thread_t)thread_to_create);
  running_thread = thread_to_create;

  swapcontext(&(tmp->attr),&(running_thread->attr));

  return 0;
}


/* passer la main à un autre thread.
 */
int thread_yield(void) {
 
  if (main_thread == NULL)
    thread_init();

  thread_info* temp;
  temp = running_thread;

 // On met ce thread en queue de liste 
  active_threads = thread_list_delete_elem(active_threads, (thread_t)running_thread);
  active_threads = thread_list_add_back(active_threads, (thread_t)running_thread);
 
 
  // On récupère le nouveau thread à executer
  running_thread = (thread_info *) thread_list_first_elem(active_threads);
 
  // On execute son contexte en le switchant avec le précédent
  swapcontext(&(temp->attr), &(running_thread->attr));



  return 0;
}


/* /\* attendre la fin d'exécution d'un thread. */
/*  * la valeur renvoyée par le thread est placée dans *retval. */
/*  * si retval est NULL, la valeur de retour est ignorée. */
/*  *\/ */
int thread_join(thread_t thread, void **retval) { 
 
  if (main_thread == NULL)
    thread_init();

  thread_info* thread_to_wait = (thread_info*)thread;

  if(thread_to_wait == main_thread){
    perror("error : do not join main");
    return 1;
  }

  // Le thread à attendre est déjà le thread courant !
  if (thread_to_wait->is_joined == 1 || thread_to_wait == running_thread){
    perror("error: thread already joined or running");
    return 1;
  }

  // On attend que le thread à attendre soit terminé
  while (thread_to_wait->state == ACTIVE || retval == NULL)
    thread_yield();
 
  // On récupère la valeur de retour du thread joint
  if (retval != NULL){
    *retval = thread_to_wait->result;
  }
  // On détruit le thread que l'on a attendu
  if (thread_to_wait->state == TO_BE_DESTROYED) {    
#ifdef DEBUG
    fprintf(stdout,"\n==== LIBERATION MEMOIRE DU THREAD %p ====\n",thread_to_wait->id_thread);
#endif
    active_threads = thread_list_delete_elem(active_threads,thread);
    free(thread_to_wait);
    thread_to_wait = NULL;
  }
  else 
    thread_to_wait->is_joined = 1;
  
  if(thread_list_size(active_threads) == 1) 
    main_free();
  
  return 0;
}


void thread_wraper(void) {
  /* Execute & exit */
  
  running_thread->result = running_thread->function(running_thread->arg);

  thread_exit(running_thread->result);
  return;
}


/* terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 *
 * L'attribut noreturn aide le compilateur à optimiser le code de
 * l'application (élimination de code mort). Attention à ne pas mettre
 * cet attribut dans votre interface tant que votre thread_exit()
 * n'est pas correctement implémenté (il ne doit jamais retourner).
 */
void thread_exit(void *retval){

  // On renvoie le résultat du thread à travers retval
  running_thread->result = retval;
  running_thread->state = TO_BE_DESTROYED;

  // On libère la mémoire allouée
  VALGRIND_STACK_DEREGISTER(running_thread->valgrind_stackid);
  free((running_thread->attr).uc_stack.ss_sp);
  
  while (1)
    thread_yield();
 
}

 
void main_free(void){          
#ifdef DEBUG
      fprintf(stdout,"\n==== LIBERATION MEMOIRE DU THREAD %p ====\n",main_thread->id_thread);
#endif
      active_threads = thread_list_delete_elem(active_threads, main_thread);
      free(main_thread->attr.uc_stack.ss_sp);
      free(main_thread);
      main_thread = NULL;
      thread_list_suppr(active_threads);

}

